Skip to content

Fix identity query hanging indefinitely outside a Tauri runtime - #3189

Open
jefflitt1 wants to merge 2 commits into
block:mainfrom
jefflitt1:upstream-identity-query-fix
Open

Fix identity query hanging indefinitely outside a Tauri runtime#3189
jefflitt1 wants to merge 2 commits into
block:mainfrom
jefflitt1:upstream-identity-query-fix

Conversation

@jefflitt1

@jefflitt1 jefflitt1 commented Jul 27, 2026

Copy link
Copy Markdown

Summary

  • The desktop chat UI can be served as a plain web page (no window.__TAURI_INTERNALS__), where get_identity() can never succeed. useIdentityQuery's queryFn always rejects in that environment, and with the default retry config the single configured retry can hit query-core's pause() path if the window isn't focused at that exact moment — canContinue() requires focusManager.isFocused() regardless of networkMode. The query then gets stuck on fetchStatus: "paused" forever instead of ever settling to "error", so anything gating UI on the query's settled status hangs indefinitely.
  • retry: false on useIdentityQuery — a missing native identity backend can't be fixed by retrying, so there's no reason to ever enter that retry/pause branch.
  • Guard get_identity/get_nsec/import_identity/persist_current_identity/sign_out with isTauri(), matching the guard convention already used elsewhere in this codebase (e.g. audioWorklet.ts), so a missing runtime produces a clean, identifiable Error instead of a raw TypeError surfaced from deep inside invoke().

Test plan

  • pnpm build (tsc + vite build) passes
  • pnpm test — 3662/3662 passing
  • pnpm exec biome check on both touched files — clean
  • Manually verified in a browser: served the built bundle as a plain web page outside Tauri and confirmed the identity query now settles to "error" immediately instead of hanging on pending/paused

Related

Checked open PRs/issues for duplicates before submitting — no duplicate found. #3027 is open in an adjacent but different subsystem (relay-side SPA routing config, vs. this PR's desktop-client Tauri guards) for a similarly-motivated self-hosted deployment; not overlapping, noted for context.

When the desktop chat UI is served as a plain web page (no
window.__TAURI_INTERNALS__), get_identity() has no way to succeed, so
useIdentityQuery's queryFn always rejects. With the default retry
config, the single configured retry can call the query-core retryer's
pause() path if the window isn't focused at that exact moment
(canContinue() requires focusManager.isFocused() regardless of
networkMode) — leaving the query stuck on fetchStatus "paused"
forever instead of ever settling to "error". Any consumer gating UI
on this query's settled status (success/error) then hangs
indefinitely, since neither state is ever reached.

- useIdentityQuery: retry: false — a missing native identity backend
  can't be fixed by retrying, so there's no reason to ever enter the
  retry/pause branch.
- tauriIdentity.ts: guard get_identity/get_nsec/import_identity/
  persist_current_identity/sign_out with isTauri(), matching the
  guard convention already used elsewhere in this codebase (e.g.
  audioWorklet.ts), so a missing runtime produces a clean, identifiable
  Error instead of a raw "Cannot read properties of undefined
  (reading 'invoke')" TypeError surfaced from deep inside invoke().
Covers the actual failure mode this PR fixes: get_identity and its
siblings must never reach invokeTauri when there is no Tauri runtime.
Without this guard the query hung on fetchStatus "paused" instead of
settling to "error" — these tests assert the guard short-circuits
before any native call is attempted, and that it doesn't when Tauri
is available.

The "Tauri available" happy-path case lives in its own file
(tauriIdentity.available.test.mjs) rather than alongside the
"unavailable" cases: node's per-process ESM module cache means mixing
an isTauri: () => true mock with an isTauri: () => false mock in the
same test file causes a later import of tauriIdentity.ts to return
the first test's cached module instance instead of re-evaluating
against the new mock. node --test already runs each matched file in
its own process, which sidesteps that.

Requires --experimental-test-module-mocks (added to the test script)
for node:test's t.mock.module — available in the project's pinned
Node version (22.22.3) but not previously enabled.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant